Skip to content

「WIP」Enhance WebMCP tool metadata, RPC reliability and add thread status services#4748

Open
lulusir wants to merge 108 commits into
mainfrom
feat/acp-v2
Open

「WIP」Enhance WebMCP tool metadata, RPC reliability and add thread status services#4748
lulusir wants to merge 108 commits into
mainfrom
feat/acp-v2

Conversation

@lulusir
Copy link
Copy Markdown
Contributor

@lulusir lulusir commented May 27, 2026

Types

  • 🎉 New Features
  • 🐛 Bug Fixes
  • 📚 Documentation Changes
  • 💄 Code Style Changes
  • 💄 Style Changes
  • 🪚 Refactors
  • 🚀 Performance Improvements
  • 🏗️ Build System
  • ⏱ Tests
  • 🧹 Chores
  • Other Changes

Background or solution

Changelog

Summary by CodeRabbit

发行说明

  • 新功能

    • 会话绑定权限对话框:权限请求现已关联到特定会话,多会话场景下避免弹窗丢失
    • 待处理权限提示:显示后台会话的权限待处理状态,支持任务切换后查看
    • WebMCP 工具组:新增文件、终端和编辑器操作工具集,扩展 IDE 能力
  • 改进

    • 线程状态可视化:聊天历史显示代理工作状态
    • 权限管理优化:移除超时自动过期,支持显式决策
  • 文档

    • 新增开发循环和场景验证规范文档

Review Change Stack

lulusir and others added 30 commits May 19, 2026 20:44
Extract hardcoded placeholder string in ACP chat view to use localize()
with i18n key, and update Chinese translation to be more concise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Integrate ClientSideConnection from the official ACP SDK instead of
building a custom JSON-RPC transport layer. The SDK provides complete
JSON-RPC 2.0 implementation, NDJSON parsing, request queuing, error
handling, and type validation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan for replacing custom JSON-RPC transport with
@agentclientprotocol/sdk's ClientSideConnection in the Node layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fix 10 issues found during plan review:
- Runtime bugs: releaseTerminal operator precedence, ndJsonStream called
  before SDK loaded, uninitialized exitResolve variable
- Interface mismatches: setSessionMode return type, sendMessage missing
  config parameter, authenticate method missing
- Behavioral gaps: handler rewrite now notes workspace sandboxing
  preservation, permission options from agent request not hardcoded
- Add test plan with unit and integration test scenarios
- Add 3 new risk items to mitigation table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… not per-connection

Replace "per WebSocket connection via childInjector" with accurate model:
single WS connection with RPC multiplexing, DI singleton services managing
one Agent process per workspace, AcpThread scoped per Agent session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Complete IAcpThread interface with all lifecycle methods shown in architecture diagram
- Add AcpThreadFactory (useFactory pattern) to auto-inject dependencies
- Update AcpAgentService.createThread to use factory instead of manual new
- Renumber all tasks (1-7) and steps to match new task structure
- Fix subsection numbering consistency throughout

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nection, and entry management

Implements the core Thread AI entity encapsulating agent process spawning,
@agentclientprotocol/sdk connection via dynamic ESM import (Node 16 compat),
entries state management, Client interface delegation, notification dispatch,
tool call state machine, and permission request forwarding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fixes 9 spec compliance deviations in AcpThread:

1. Entry data contracts use SDK types (ContentBlock, ToolCall, Plan)
   with data wrapper pattern instead of flattened primitives
2. Event model: replaced bulk entries_changed with granular
   entry_added/entry_updated events
3. markAssistantComplete(): no params — finds last assistant entry
   automatically, transitions status to awaiting_prompt
4. respondToToolCall(toolCallId, allowed: boolean): updates
   ToolCallEntry.status (completed/rejected), fires entry_updated
5. reset(): preserves _initialized flag for thread pool reuse
6. initialize(): accepts AgentProcessConfig parameter
7. sessionId: non-nullable string (empty when unbound)
8. Added setError(error): sets status to errored, fires events
9. handleNotification: made public per IAcpThread interface

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… instances

Add AcpThreadFactoryToken, AcpThreadFactory type, AcpThreadRuntimeConfig,
and AcpThreadFactoryProvider using OpenSumi DI useFactory pattern with
Injector-based dependency resolution. The factory accepts sessionId and
runtime config (command, args, cwd, env) at call time while injecting
file system handler, terminal handler, and permission caller via DI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tor plan

- Rewrite file-system handler: replace generic FileSystemRequest/Response
  with typed ReadTextFileRequest/Response and WriteTextFileRequest/Response,
  remove permissionCallback, getFileMeta, listDirectory, createDirectory
- Rewrite terminal handler: change method signatures to accept individual
  parameters (terminalId, sessionId) instead of request objects for
  getTerminalOutput, waitForTerminalExit, killTerminal, releaseTerminal
- Remove @Injectable decorator and permissionCallback from both handlers
- Update AcpThread Client and AcpAgentRequestHandler to call handlers
  with new signatures
- Fix pre-existing PlanEntry export error in index.ts
- Update tests to match new interfaces, remove obsolete test cases

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…support

Refactor the permission caller from a per-clientId static singleton pattern
(AcpPermissionCallerManager) to a proper DI singleton service
(AcpPermissionCallerService) that extends RPCService. Add a new
PermissionRoutingService to route permission requests from multiple ACP
sessions independently, with session registration, active session fallback,
and concurrent request support. Pass sessionId through the entire chain
from Node caller to browser dialog for multi-dialog tracking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the single-process model (AcpCliClientService + CliAgentProcessManager)
with a multi-thread pool architecture using AcpThread instances. Key changes:

- Thread pool management with sessions Map + threadPool array (max 10)
- findOrCreateThread with idle thread reuse logic
- createSession using Deferred pattern (no setTimeout polling)
- sendMessage with streaming via SumiReadableStream + thread.onEvent
- disposeSession with default (return to pool) and force (full dispose) modes
- stopAgent disposes all threads and clears pool
- AgentUpdate mapping from SDK sessionNotification events

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The AcpPermissionCallerService.requestPermission() signature now requires
sessionId as a second parameter. Update all call sites in
agent-request.handler.ts and corresponding test assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… update DI bindings

- Replace AcpPermissionCallerManager with PermissionRoutingService in
  AcpThreadFactoryProvider so permission requests go through the routing layer
- Update AcpThreadOptions.permissionCaller to permissionRouting
- Update backServices to bind AcpPermissionServicePath to
  AcpPermissionCallerServiceToken instead of deprecated alias
- Update acp-thread.test.ts mock to match new permissionRouting interface

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…teSession

Previously createSession generated a fake uuid and passed it to
loadSessionOrNew, which failed and fell back to newSession. The real
sessionId from the agent CLI was stored in the thread but not used for
the sessions map or return value.

The new flow:
1. Find or create an idle thread without sessionId binding
2. Call thread.newSession() to get the real sessionId from the agent CLI
3. Register the thread with the real sessionId
4. Return the real sessionId to callers

Also adds error-handling cleanup in loadSession to prevent thread leaks
when initialization or loadSession fails on a newly created thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add setSessionMode, setSessionConfigOption, and unstable session
operations (fork, resume, close, setSessionModel) to IAcpThread
and AcpThread class. Also add 12 SDK type re-exports to acp-types.ts.

Unify cancel() to use ensureInitialized() for consistent error
behavior across all methods — previously it silently returned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nager, update AcpAgentService

Removes the deprecated CLI client and process manager classes that have
been superseded by the thread pool pattern. Cleans up DI bindings,
barrel exports, and associated test files. Updates AcpAgentService,
AcpChatAgent, and agent-types to align with the new architecture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pAgentService to AcpThread

AcpThread owns SDK notification format knowledge, so translating
SessionNotification into the legacy AgentUpdate stream format belongs
there. AcpAgentService now delegates to thread.toAgentUpdate() instead
of parsing SDK internals itself.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap thread.setSessionMode() in try/catch with warn logging, consistent
with cancelRequest pattern. Re-throw error so caller is notified of failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Track whether thread was newly created or reused. On failure:
- New thread: remove from pool and dispose
- Reused thread: reset to clean state
- Add error logging consistent with loadSession pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap thread.setSessionConfigOption() in try/catch with warn logging,
consistent with setSessionMode pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…AcpThread

Expose four unstable_* methods from AcpThread through IAcpAgentService
without the unstable_ prefix, enabling session lifecycle management
(fork, resume, close) and model switching from the service layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wrap fork/resume/close/setSessionModel delegations in try/catch with
warn logging, consistent with setSessionMode/setSessionConfigOption pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…licate

Previously listSessions only read from the pool's this.sessions Map, which
could drift from the agent's actual state. Now it delegates to each active
thread's listSessions() method, merges results with Set deduplication, and
catches per-thread errors with warn logging.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ionInfo type

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lulusir and others added 26 commits May 26, 2026 19:50
Update createThreadInstance and findOrCreateIdleThread to forward the new
agentId and nodePath fields from AgentProcessConfig to the thread factory.
…props

The base ChatHistory component doesn't render the badge (handled by
ACP-specific registered components). Remove the unused prop from
IChatHistoryProps and the fallback call site.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace manual config assembly with buildAcpAgentProcessConfig pure function
call. Read ai-native.acp.nodePath and ai-native.acp.agents preferences and
pass them as userPreferences for merging with registration defaults.
The base ChatHistory.tsx component was missing the pending permission
badge and key icon that ACP variants already had. Also adds the badge
prop to the fallback ChatHistory render in chat.view.acp.tsx and the
i18n keys for the tooltip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…and WebMCP refactor

- Migrate ACP agent subprocess startup from env vars to OpenSumi preferences
- Add agentId, toAgentUpdate, setSessionMode, permissionRouting to test mocks
- Refactor WebMCP tools registry (1100+ line reduction)
- Consolidate BDD scenarios - remove duplicates, update existing ones
- Wire DefaultACPConfigProvider with buildAcpAgentProcessConfig
- Add thread status caller service integration
- Update cross-platform path handling in spawn config

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Design for progressively exposing IDE capabilities to AI agents
via ACP extension methods, organized by WebMCP groups with
on-demand loading to manage context window usage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add `details` field to WebMcpToolResult for human-readable errors
- Widen execute return type to `Promise<any>` for compatibility
- Clarify ACP extension method mechanism (dynamic registration via
  method table, Method not found for unloaded groups)
- Clarify default-loaded auto-registration timing and agent behavior
- Fix `{file}` → `{path}` naming inconsistency across all groups
- Rename `loadedToolCount` → `totalLoadedToolCount` for clarity
- Define P2 group tool methods with parameters and service dependencies
- Group files are new source-of-truth, not extracted from registries
- Add `webmcp-utils.ts` for shared helpers (tryGetService, etc.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
12-task plan covering P0 infrastructure (types, utils, registry, RPC
bridge, handler, extMethod hook, DI wiring) and P1 default groups
(file, terminal, editor) with integration tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Show a badge on the history button and a bell icon on items with
pending permission requests, so users can see at a glance that another
session needs their attention without opening the popover.

Changes:
- AcpChatViewHeader: inject AcpPermissionBridgeService, subscribe to
  onPendingCountChange/onActiveSessionChange, populate hasPendingPermission
- AcpChatHistory/ChatHistory.acp/ChatHistory: render amber bell icon
  (mutually exclusive with thread-status icon) for pending items
- chat-history.module.less: add .chat_history_item_pending styling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ility declaration

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Export WebMcpGroupRegistry, AcpWebMcpRpcService, and webmcp-utils
  from browser/acp/index.ts
- Export AcpWebMcpCallerService and AcpWebMcpHandler from
  node/acp/index.ts
- Register WebMcpGroupRegistry and AcpWebMcpRpcService as providers
  in the browser module with backService for RPC bridge
- Register AcpWebMcpCallerService as provider and backService in the
  node module, following the AcpPermissionCallerService pattern
- Inject AcpWebMcpCallerService into AcpThreadFactoryProvider and
  pass it to AcpThread constructor via webmcpCallerService option

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Also fix a TS2352 cast error in acp-webmcp-handler.ts where
WebMcpToolResult needed a double cast through unknown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rebuild core-common to regenerate .d.ts with new WebMCP types
  (AcpWebMcpBridgePath, WebMcpGroupRegistryToken, etc.)
- Fix tryGetService type signature: use Token | symbol instead of unknown
- Add missing ErrorCode variants: INVALID_INPUT, IS_DIRECTORY, NOT_A_DIRECTORY
- Fix FileStat.mtime -> FileStat.lastModification (OpenSumi API)
- Remove unsupported recursive option from fileService.delete() calls
- Remove duplicate IDisposable import in ai-core.contribution.ts
- Add non-null assertion for RPCService.client in acp-webmcp-caller.service

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…g issue

AcpWebMcpHandler.initialize() was called during AcpThread.ensureSdkConnection()
before the RPC client was ready, causing TypeError: Cannot read properties of
undefined (reading '$getGroupDefinitions'). Changed to ensureInitialized() with
lazy init — group definitions are fetched on first _opensumi/* call instead.

Also removed debug console.log and unused import from acp-thread.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…eliability

- Return full tool metadata (method, description, inputSchema) in list_groups and load_group
- Restructure capability meta under webmcp namespace with methods list
- Add staticRpcClient fallback in AcpWebMcpCallerService for cross-injector scope calls
- Eagerly initialize WebMCP handler before ACP init to ensure groups are ready
- Proper -32601 error response for unhandled extMethod calls
- Add thread status caller/RPC services and webmcp types/polyfill
- Add debug logging throughout WebMCP handler and AcpThread extMethod flow
- Fix chat history item key to use updatedAt for proper re-rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Walkthrough

实现 WebMCP 分组与工具注册及 RPC 桥,新增线程状态 RPC/呈现,重构权限对话为会话绑定并移除自动超时;更新 ACP/MCP 配置与偏好;替换聊天历史组件与输入触发;大量单测新增/更新;同时新增/整合 dev-loop 与多份 ACP/WebMCP 设计与实施文档。

Changes

运行时:ACP × WebMCP 分组、会话绑定权限、线程状态与配置

Layer / File(s) Summary
会话绑定权限对话:桥接、容器与 UI
packages/ai-native/src/browser/acp/*permission*, packages/ai-native/src/browser/components/*permission*, packages/ai-native/src/browser/acp/components/*, packages/ai-native/src/browser/components/*chat-history*.*, packages/ai-native/src/browser/components/acp/chat-history.module.less, packages/ai-native/src/browser/components/chat-history.module.less, packages/ai-native/__test__/browser/*permission*
权限桥接支持 active session 与 pending 索引;RPC 传递 sessionId;对话容器/小部件按会话过滤;历史列表与角标样式与渲染;相关单测覆盖。
线程状态通知与 UI 呈现
packages/ai-native/src/browser/acp/acp-thread-status-rpc.service.ts, packages/ai-native/src/browser/chat/*, packages/ai-native/src/browser/index.ts, packages/ai-native/src/browser/acp/index.ts, packages/ai-native/__test__/node/*thread-status*, packages/ai-native/__test__/node/acp-cli-back.test.ts
浏览端接收线程状态并更新 ChatModel,ChatAgent 处理 threadStatus,历史视图订阅;DI 注册与状态转发测试完善。
WebMCP 分组与工具注册、桥接与测试
packages/ai-native/src/browser/acp/webmcp-*.ts, packages/ai-native/src/browser/ai-core.contribution.ts, packages/ai-native/src/browser/index.ts, packages/ai-native/__test__/browser/webmcp-tools.test.ts, packages/ai-native/__test__/node/acp-webmcp-handler.test.ts
实现工具结果与错误分类、分组注册表与默认组、RPC 桥、file/editor/terminal 三组工具及初始化注册;ACP 工具注册与 Node 端 handler 单测。
ACP/MCP 配置与偏好集成
packages/ai-native/src/browser/chat/default-acp-config-provider.ts, packages/ai-native/src/browser/mcp/config/mcp-config.service.ts, packages/ai-native/src/browser/acp/build-agent-process-config.ts, packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts, packages/ai-native/src/browser/preferences/schema.ts, packages/ai-native/package.json, packages/ai-native/__test__/browser/acp/build-agent-process-config.test.ts
从偏好聚合 ACP 服务器,合成 Agent 进程配置,更新偏好项与 MCP 工具 schema 转换;依赖版本更新与配套测试。
聊天视图集成与输入行为调整
packages/ai-native/src/browser/chat/chat.view.acp.tsx, packages/ai-native/src/browser/components/ChatHistory.tsx, packages/ai-native/src/browser/components/ChatHistory.acp.tsx, packages/ai-native/src/browser/components/acp/MentionInput.tsx, packages/ai-native/src/browser/acp/components/AcpChatInput.tsx
接入 ACP 历史组件与本地化占位;移除旧历史组件;“/命令”仅首个非空白触发;补充测试标识。
Node 侧服务与处理器测试调整与补充
packages/ai-native/__test__/node/*
重构/扩展 AcpAgentService、CliBack、Terminal/FileSystem/PermissionCaller、PermissionRouting 与 AcpThread 测试;微调请求处理器断言。

文档与技能:Dev Loop、CDP 验证、WebMCP 设计/实施计划

Layer / File(s) Summary
技能文档:dev-loop 新增、contract-dev 合并、cdp 验证补充
.claude/skills/*, .gitignore
新增 dev-loop 技能全流程;contract-dev 合并提示;cdp 验证补充 Phase 0 与参考表;忽略 .claude/
ACP 设计/实施计划:Node SDK 重构、Full Delegation、会话绑定权限
docs/superpowers/plans/*-acp-*, docs/superpowers/specs/*permission*, docs/superpowers/specs/*delegation*
规划 AcpThread/AcpAgentService 重构与能力透传;会话绑定权限对话的设计、实施与验收。
Dev Loop 与 WebMCP 工具标准/注册、分组设计与示例
docs/superpowers/specs/*dev-loop*, docs/superpowers/specs/*webmcp-*, docs/superpowers/specs/*testing-example*, docs/superpowers/specs/*background-permission*, docs/superpowers/plans/2026-05-26-acp-webmcp-groups.md
定义 Dev Loop 设计、WebMCP 工具粒度与注册设计、分组设计、E2E 示例与后台权限可见性设计。

Sequence Diagram(s)

sequenceDiagram
  participant Node as Node.ACP Agent
  participant Thread as AcpThreadStatusRpcService
  participant Chat as ChatAgent/ChatModel
  participant UI as AcpChatHistory/AcpChatViewHeader
  Node->>Thread: $onThreadStatusChange(sessionId, status)
  Thread->>Chat: model.setThreadStatus(status)
  Chat-->>UI: onThreadStatusChange event
  UI-->>UI: 更新会话历史项状态图标
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • opensumi/core#4404 — 同样涉及聊天历史组件与样式调整的相关改动路径,可能与本次“历史角标/状态图标”改动相邻。

Suggested labels

🎨 feature

Suggested reviewers

  • erha19
  • life2015
  • Aaaaash
  • Ricbet
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/acp-v2

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (16)
docs/superpowers/specs/2026-05-21-acp-thread-full-delegation-design.md-33-60 (1)

33-60: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请为示意代码块补充语言标识。

这两处建议补 text(或更贴近内容的语言),避免 MD040。

Also applies to: 66-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-21-acp-thread-full-delegation-design.md`
around lines 33 - 60, 为两个示意代码块补上语言标识以修复 MD040:在包含接口/类示意图的代码块(显示 IAcpAgentService
和 AcpThread 的那一段)以及文件中另一个相同类型的代码块(评论中提到的 66-68 行附近),把开头的 ``` 改为
```text(或使用更贴近的语言标识),确保示意图的代码块标记为 text 以消除 MD040 警告并保持渲染一致;定位时参考符号
IAcpAgentService 和 AcpThread 以找到对应段落并修改代码块标记。
.claude/skills/cdp-verification-scenarios/SKILL.md-90-95 (1)

90-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

未标注语言的代码块建议统一补齐。

以上几个代码块建议补充语言(如 text/md),否则会持续触发 MD040。

Also applies to: 130-144, 150-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/cdp-verification-scenarios/SKILL.md around lines 90 - 95, The
fenced code blocks containing the checklist lines ("Observe actual state (via
CDP or WebMCP)" through "Output explicit judgment: PASS or FAIL") and the other
blocks at the indicated ranges are missing a language tag, causing MD040; update
each triple-backtick fence to include a language token such as ```text or ```md
(e.g., change ``` to ```text) for the blocks that contain the checklist and the
blocks at ranges 130-144 and 150-168 so the linter stops flagging them.
.claude/skills/dev-loop/SKILL.md-25-29 (1)

25-29: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

为代码块补充语言标识以消除 Markdown Lint 告警。

这里的 fenced code block 未指定语言,建议改为 text(流程图文本)或合适语言,避免 MD040 告警。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/dev-loop/SKILL.md around lines 25 - 29, Add a language
identifier to the fenced code block that contains the flow diagram (the block
starting with "Phase 0: 环境准备..."), e.g. change the opening fence from ``` to
```text so the block becomes ```text ... ```, which will satisfy Markdown lint
rule MD040.
docs/superpowers/plans/2026-05-20-acp-node-sdk-refactor.md-21-57 (1)

21-57: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

建议批量补全代码块语言并修复 blockquote 空行。

当前文档存在多处 MD040/MD028 告警,建议统一补全 fenced code block 语言(例如 text/typescript/bash)并移除 blockquote 内空行,避免后续文档 lint 噪音。

Also applies to: 63-180, 184-237, 241-1240

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-20-acp-node-sdk-refactor.md` around lines 21 -
57, The markdown has many lint warnings (MD040/MD028) because fenced code blocks
lack language identifiers and blockquotes contain extra blank lines; update all
fenced code blocks in this document (including the sections showing AcpThread,
AcpAgentService, AcpPermissionRpcService, AcpCliBackService,
AcpFileSystemHandler, AcpTerminalHandler diagrams and the long plan sections
referenced) to include an appropriate language token (e.g., text, typescript,
bash) and remove any blank lines inside blockquote sections so blockquotes are
continuous; ensure you apply the same fixes to the other ranges called out
(lines 63-180, 184-237, 241-1240) to eliminate MD040/MD028 warnings.
docs/superpowers/specs/2026-05-22-session-bound-permission-dialogs-design.md-32-52 (1)

32-52: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

补充代码块语言标识,避免 markdownlint 告警。

这里的 fenced code block 建议统一改为 ```text(或对应语言),可消除 MD040 并避免后续文档 CI 失败。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-22-session-bound-permission-dialogs-design.md`
around lines 32 - 52, The fenced code blocks in this document (showing the old
and new flow) lack a language identifier and trigger markdownlint MD040; update
both blocks to use a language tag (for example ```text) so the blocks containing
the flow diagrams (references: the code blocks under the "Node: AcpThread ..."
diagrams) include a language marker and resolve CI/markdownlint failures; ensure
both occurrences around the "Old Flow" and "New Flow" diagrams use the same tag.
docs/superpowers/specs/2026-05-25-dev-loop-design.md-245-267 (1)

245-267: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

目录迁移说明与本 PR实施目标不一致。

这里写的是 webmcp-examples.md 被移动到 dev-loop/reference/,但当前计划与变更目标是删除该文件(并以 webmcp-tool-registrar/CODE-PATTERNS.md 替代)。建议统一为单一结论,避免后续执行时产生歧义。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-25-dev-loop-design.md` around lines 245 - 267,
The tree snippet claims webmcp-examples.md is moved to dev-loop/reference/ but
the PR intent is to delete it and replace its content with
webmcp-tool-registrar/CODE-PATTERNS.md; update the spec so the entry for
dev-loop/reference/ does not list webmcp-examples.md and instead documents that
webmcp-examples.md is removed and its guidance is superseded by
webmcp-tool-registrar/CODE-PATTERNS.md (referencing webmcp-examples.md,
dev-loop/reference, and webmcp-tool-registrar/CODE-PATTERNS.md to locate the
relevant files).
docs/superpowers/specs/2026-05-22-webmcp-tool-granularity.md-31-190 (1)

31-190: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

统一为 fenced code block 添加语言声明。

当前多处代码块缺少语言标记,建议统一标注(如 text/javascript),以消除 markdownlint MD040。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-22-webmcp-tool-granularity.md` around lines 31
- 190, Several fenced code blocks in this spec (e.g., the examples showing
startFileCreation vs createFile, the return-semantic examples, the usage
snippets for searchFiles/getEditorState/runDiagnostics, the anti-pattern code
blocks like navigator.modelContext.registerTool examples, and the ASCII
flowchart) lack language tags and trigger markdownlint MD040; update each
triple-backtick fence to include an appropriate language hint (use javascript
for JS snippets, text for prose/ASCII diagrams, or json when showing JSON-like
returns) so tools and linters correctly highlight and MD040 is resolved.
docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md-231-547 (1)

231-547: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

文档代码块建议补齐语言类型声明。

多个 fenced code block 缺少语言标识(如 bash/markdown/text),建议统一补齐,避免 markdownlint 报警并提升渲染一致性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md` around
lines 231 - 547, Several fenced code blocks in the dev-loop plan (the large
Markdown content in
docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md) lack
language identifiers; add appropriate language tags to each triple-backtick
fence (e.g., markdown for Markdown snippets, bash for shell commands, javascript
for CDP/WebMCP snippets, json for configuration) so blocks such as the
Scenario/Phase examples, the shell/git commands, the JSON config, and the CDP
evaluate_script example are explicitly annotated and avoid markdownlint
warnings.
docs/superpowers/specs/2026-05-22-webmcp-tool-registration-design.md-22-325 (1)

22-325: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

补充代码块语言标识以通过文档 lint。

文档中多处 fenced code block 未指定语言,建议按内容补 text/typescript/bash,减少 CI 噪音并提升可读性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-22-webmcp-tool-registration-design.md` around
lines 22 - 325, The doc has many fenced code blocks without language identifiers
which trips the linter; update each triple-backtick block in this file
(including the ASCII diagram, the TypeScript example for registerAcpWebMCPTools,
the flow snippets, tables and shell snippets) to include an appropriate language
tag such as text, typescript, or bash (e.g., replace ``` with ```text or
```typescript / ```bash as appropriate) so examples like the
webmcp-tools.registry.ts snippet and the codegraph_explore/flow blocks are
lint-clean.
docs/superpowers/specs/2026-05-26-background-permission-notification-design.md-49-82 (1)

49-82: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

该代码块需要补充语言标识。

此处 fenced code block 未指定语言,建议改为如 ```text(或更精确语言)以消除 MD040 告警。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/superpowers/specs/2026-05-26-background-permission-notification-design.md`
around lines 49 - 82, The fenced code block in the spec lacks a language
identifier which triggers MD040; update the block opening from ``` to include a
language (e.g., ```text or a more specific language) so the markdown linter is
happy—apply this change to the block that documents the Node layer and Browser
layer (containing symbols like AcpThread.handlePermissionRequest,
AcpPermissionCallerService.requestPermission, AcpPermissionBridgeService, and
methods
showPermissionDialog/handleUserDecision/getPendingCountExcludingActive/hasPendingForSession).
docs/superpowers/specs/2026-05-22-acp-webmcp-testing-example.md-45-47 (1)

45-47: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

请为所有 fenced code block 显式标注语言。

当前多处三反引号代码块未声明语言,已触发 MD040。建议按内容补齐语言标识(例如 textjsontsxmermaid),以保证 lint 通过与渲染一致性。

Also applies to: 53-55, 63-72, 80-82, 96-98, 106-119, 125-131, 137-151, 155-158, 162-165, 175-189, 199-201, 233-240, 244-250, 254-259, 265-298

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-22-acp-webmcp-testing-example.md` around lines
45 - 47, In the markdown file 2026-05-22-acp-webmcp-testing-example.md, many
fenced code blocks are missing language identifiers (triggering MD040); update
every triple-backtick block to include an explicit language tag (e.g., ```text,
```json, ```tsx, ```mermaid) matching the block content—for example the snippet
"Agent 通过 Chrome DevTools MCP 连接到打开的 IDE 页面 (http://localhost:8080)" should be
wrapped as ```text; search the file for all fenced blocks (the ones called out
in the review) and add the appropriate language specifier so linting and
rendering are consistent.
packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx-199-199 (1)

199-199: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

渲染了布尔值导致意外文本输出。

{item.hasPendingPermission} 在 JSX 中直接渲染布尔值,当值为 true 时会在 DOM 中输出字面文本 "true"。这行代码看起来是调试残留,应该删除。

🐛 建议修复
         onClick={() => handleHistoryItemSelect(item)}
         >
-          {item.hasPendingPermission}
           <div className={styles.chat_history_item_content}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx` at line
199, Remove the stray direct rendering of the boolean value
item.hasPendingPermission in the AcpChatHistory component (it causes "true" to
appear in the DOM); locate the JSX that contains "{item.hasPendingPermission}"
and either delete that expression or replace it with a proper conditional
UI/indicator (e.g., render an icon or text only when item.hasPendingPermission
is true using a conditional render) so the boolean itself is not output to the
DOM.
packages/ai-native/__test__/browser/permission-dialog-ui.test.tsx-104-105 (1)

104-105: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

避免在用例间复用同一个 mockPermissionBridge 实例。

当前实例在文件级单例创建,内部 listeners 闭包会跨测试累积,容易引入测试间污染与偶发失败。建议在每个 beforeEach 里重建并注入。

建议修改
-const mockPermissionBridge = createMockPermissionBridgeService();
+let mockPermissionBridge: ReturnType<typeof createMockPermissionBridgeService>;

 describe('PermissionDialogWidget - Rendering', () => {
@@
   beforeEach(() => {
+    mockPermissionBridge = createMockPermissionBridgeService();
     container = document.createElement('div');
     document.body.appendChild(container);
     jest.clearAllMocks();
@@
 describe('PermissionDialogWidget - Keyboard Navigation', () => {
@@
   beforeEach(() => {
+    mockPermissionBridge = createMockPermissionBridgeService();
     container = document.createElement('div');
     document.body.appendChild(container);
     jest.clearAllMocks();
@@
 describe('PermissionDialogWidget - Session Isolation', () => {
@@
   beforeEach(() => {
+    mockPermissionBridge = createMockPermissionBridgeService();
     container = document.createElement('div');
     document.body.appendChild(container);
     jest.clearAllMocks();

Also applies to: 138-144, 250-256, 409-414

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/browser/permission-dialog-ui.test.tsx` around
lines 104 - 105, The file-level single instance mockPermissionBridge created via
createMockPermissionBridgeService() is reused across tests causing listener
state to leak; remove the top-level instantiation and instead recreate and
inject a fresh mock by calling createMockPermissionBridgeService() inside a
beforeEach, then use that per-test mock when mounting components or wiring
services (reference the mockPermissionBridge identifier and
createMockPermissionBridgeService() factory and update any setups that currently
rely on the shared instance); ensure any afterEach cleanup detaches listeners if
applicable so each test starts with a clean mock.
packages/ai-native/src/browser/acp/webmcp-groups/terminal.webmcp-group.ts-60-84 (1)

60-84: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

name 参数对外声明了但实际未生效

Line 60-63 在 schema 里暴露了 name,但 Line 81-84 创建终端时未使用该字段。建议二选一:要么透传并生效,要么从 schema/描述中移除,避免误导调用方。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/webmcp-groups/terminal.webmcp-group.ts`
around lines 60 - 84, The schema exposes a name field but it is never used when
creating the terminal; update the execute logic to pass params.name into
terminalController.createTerminal (e.g., include a displayName or name property
in the options object) so the declared name becomes effective, or if you prefer
not to support naming remove the name entry from the schema/description to avoid
misleading callers; locate the execute function where
terminalController.createTerminal is called and either add the name propagation
(params.name as string) to the createTerminal options or remove the name schema
entry.
packages/ai-native/__test__/browser/webmcp-tools.test.ts-135-141 (1)

135-141: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

工具数量断言依赖全局状态,易导致跨测试用例不稳定

Line 137 直接断言 getTools().length === 12 对全局 navigator.modelContext 过于敏感。建议先筛选 acp_ 前缀再断言数量和字段,避免其他测试注册工具时误失败。

🔧 建议修复
-      const tools = navigator.modelContext!.getTools();
-      expect(tools.length).toBe(12); // 12 ACP tools
-      for (const tool of tools) {
+      const tools = navigator.modelContext!.getTools().filter((t) => t.name.startsWith('acp_'));
+      expect(tools.length).toBe(12);
+      for (const tool of tools) {
         expect(tool).not.toHaveProperty('execute');
         expect(tool.name).toMatch(/^acp_\w+$/);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/browser/webmcp-tools.test.ts` around lines 135 -
141, The test currently asserts navigator.modelContext!.getTools().length === 12
which is brittle; instead call navigator.modelContext!.getTools(), filter the
result for tools whose name starts with 'acp_' (e.g.
tool.name.match(/^acp_\w+$/) or startsWith('acp_')), then assert the filtered
array length is 12 and loop over the filtered tools to assert they lack an
'execute' property and their names match the expected pattern; update assertions
to use the filtered list (references: getTools, navigator.modelContext,
tool.name).
packages/ai-native/__test__/node/acp-thread-status-caller.test.ts-64-68 (1)

64-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

异步拒绝分支断言存在漏检风险

这里用 not.toThrow() 只能验证同步异常,不能验证 Promise reject 是否被正确吞掉,可能产生假阳性。建议改为可等待断言。

建议修改
 it('should silently ignore RPC call rejection', async () => {
   mockRpcClient.$onThreadStatusChange.mockRejectedValue(new Error('RPC disconnected'));

-  expect(() => service.notifyThreadStatusChange('session-1', 'working')).not.toThrow();
+  await expect(
+    Promise.resolve(service.notifyThreadStatusChange('session-1', 'working')),
+  ).resolves.toBeUndefined();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/node/acp-thread-status-caller.test.ts` around
lines 64 - 68, The test currently uses a synchronous check
(expect(...).not.toThrow()) which won't catch Promise rejections; change the
assertion to await the async call and assert it resolves (e.g., await
expect(service.notifyThreadStatusChange('session-1',
'working')).resolves.toBeUndefined()) so the mocked rejection from
mockRpcClient.$onThreadStatusChange is properly asserted as swallowed by
notifyThreadStatusChange.
🧹 Nitpick comments (3)
docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md (1)

79-87: ⚡ Quick win

“无自动超时”方案里不应再用 undefined as unknown as NodeJS.Timeout

这会引导实现者用类型断言掩盖模型问题。建议把 pendingDecisions.timeout 改为可选字段,或在“无超时路径”完全移除该字段。

建议修正文档示例
 return new Promise((resolve) => {
   this.pendingDecisions.set(requestId, {
     resolve,
-    timeout: undefined as unknown as NodeJS.Timeout,
   });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md` around
lines 79 - 87, The example Promise stores a pending decision with a bogus cast
`undefined as unknown as NodeJS.Timeout`; change the shape so `pendingDecisions`
entries make `timeout` optional (e.g., timeout?: NodeJS.Timeout) or omit
`timeout` entirely in the "no-auto-timeout" path instead of asserting types;
update the code that reads or clears `pendingDecisions.get(requestId)?.timeout`
to handle the optional field and any callers of the structure (look for uses of
pendingDecisions, requestId, and timeout in resolve/clear logic) so no
type-unsafe assertions remain.
packages/ai-native/src/browser/acp/build-agent-process-config.ts (1)

8-35: 💤 Low value

确认 nodePath 空字符串转 undefined 的语义是否符合预期。

Line 29 使用 input.userPreferences.nodePath || undefined,会将空字符串 '' 转换为 undefined。测试用例 line 104-111 验证了此行为,但需确认这是否符合业务语义:用户显式配置空字符串与未配置的含义是否相同?若两者应有区分(如空字符串表示"禁用自定义 node 路径"),则应使用 || undefined 之外的逻辑。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/build-agent-process-config.ts` around
lines 8 - 35, The current buildAcpAgentProcessConfig uses
input.userPreferences.nodePath || undefined which collapses an explicit empty
string to undefined; update the assignment for nodePath in
buildAcpAgentProcessConfig to preserve an explicit empty string (i.e. only
convert to undefined when nodePath is strictly undefined) by replacing the
truthy-coercion with an explicit check against undefined, and adjust tests or
add a new test to cover the distinct behavior between "" and undefined for
userPreferences.nodePath.
packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx (1)

215-220: 💤 Low value

移除注释掉的调试代码。

建议删除这段注释掉的调试代码以保持代码整洁。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx` around
lines 215 - 220, Remove the commented-out debug span block inside the
AcpChatHistory component: delete the entire commented JSX that contains
data-testid={`thread-status-${item.id}`} and references item.threadStatus and
item.loading so the file no longer contains commented debugging code; after
removal, run a quick lint/format pass to ensure the JSX remains well-formed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md`:
- Around line 159-167: useEffect 中当前直接 return unsubscribe 导致不是一个 cleanup
function;把订阅的返回值(unsubscribe)包成一个函数并在该函数中调用其 dispose() 方法。定位到使用
useEffect、permissionBridgeService.onActiveSessionChange、unsubscribe、permissionBridgeService.getActiveSession
和 setActiveSessionId 的代码,把 return unsubscribe; 改为 return () =>
unsubscribe.dispose();(若 onActiveSessionChange 返回的是一个函数而非对象则改为 return () =>
unsubscribe();)。

In `@docs/superpowers/plans/2026-05-26-acp-webmcp-groups.md`:
- Around line 518-523: The parsing logic incorrectly expects parts[0] to be an
empty string which rejects valid methods like "_opensumi/file/read"; update the
conditional around method, parts, groupName and toolAction so it explicitly
checks that parts.length === 3, parts[0] === '_opensumi' (the prefix), and
parts[1] is non-empty, and only then assign groupName = parts[1] and toolAction
= parts[2]; adjust the error branch to return TOOL_NOT_FOUND when these explicit
checks fail.

In `@docs/superpowers/specs/2026-05-26-acp-webmcp-groups-design.md`:
- Around line 60-63: The doc's described behavior (dynamic registration of
`_opensumi/{group}/*` on load_group/unload_group and JSON-RPC -32601 when not
loaded) is inconsistent with the current implementation which routes all calls
through extMethod and returns a business error TOOL_NOT_LOADED; update either
the docs or the implementation to match: either change the docs in this spec to
describe the extMethod routing and TOOL_NOT_LOADED response (referencing
load_group/unload_group and ACP Server notification semantics), or modify the
implementation so ACP Server actually registers/unregisters methods at
load_group/unload_group and returns standard JSON-RPC Method not found (-32601)
when a group method is not loaded; ensure you reference extMethod,
TOOL_NOT_LOADED, load_group, unload_group and ACP Server in the chosen fix so
SDK/tests follow the correct contract.

In `@packages/ai-native/__test__/node/acp/acp-thread.test.ts`:
- Around line 23-30: The Jest mock for stream/web doesn't match the actual
import used in acp-thread.ts (which imports from 'node:stream/web'), so update
the mock target in acp-thread.test.ts to jest.mock('node:stream/web', ...) and
ensure it exports MockReadableStream and MockWritableStream matching the
original mock's shape; locate the existing MockReadableStream/MockWritableStream
definitions in the test and replace the module specifier string only so the mock
applies to the import used by the code under test.
- Around line 272-290: The test currently installs its own 'exit' handler and
directly mutates private fields, which bypasses AcpThread's real exit handling;
instead assign the mock child process to (thread as any)._childProcess (using
createMockChildProcess), do not add an 'exit' listener in the test, then emit
'exit' on that mock so AcpThread's internal handler (which updates
_processRunning, _connected and status) runs; assert (thread as
any)._processRunning is false, (thread as any)._connected is false and
thread.status is 'disconnected'. Use the same symbols: _childProcess,
_processRunning, _connected and status on the AcpThread instance so the test
verifies the class's real logic.

In `@packages/ai-native/__test__/node/permission-routing.test.ts`:
- Around line 60-88: The tests contain missing awaits and empty assertions: in
the 'should register a session' case await the async call to
service.routePermissionRequest(baseRequest, 'sess-1') and assert
mockCallerService.requestPermission was called with (baseRequest, 'sess-1') and
optionally assert the returned outcome equals the mocked response; in 'should
unregister a session' await service.routePermissionRequest(baseRequest,
'sess-1') after unregisterSession('sess-1') and assert the result is the
cancelled outcome (or that mockCallerService.requestPermission was not called)
using expect(...).toEqual(...) or toHaveBeenCalledTimes(0); in 'should not
affect other sessions when unregistering one' await
service.routePermissionRequest(baseRequest, 'sess-2') and assert
mockCallerService.requestPermission was called with (baseRequest, 'sess-2') to
prove sess-2 remains routable. Ensure all routePermissionRequest calls are
awaited and assertions use mockCallerService.requestPermission and returned
values.

In `@packages/ai-native/package.json`:
- Around line 63-64: The package currently allows zod v3 and v4 but the MCP
proxy (packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts) only
calls tool.inputSchema.toJSONSchema() when present, so running with Zod v3
leaves a non-JSON Schema passed to ListTools; either tighten package.json "zod"
to "^4.0.0" or add a v3 fallback: import zod-to-json-schema and, inside the code
path that reads tool.inputSchema (in mcp-server-proxy.service.ts), when
inputSchema.toJSONSchema is undefined call zodToJsonSchema(inputSchema) and
ensure the returned value is a valid JSON Schema before sending to MCP; also
review packages/ai-native/src/common/tool-invocation-registry.ts for any
Zod-4-only usage (e.g., z.record(z.string(), ...)) and adjust or guard
imports/types if you choose to keep v3 support.

In `@packages/ai-native/src/browser/acp/permission-bridge.service.ts`:
- Around line 244-279: In clearSessionDialogs, the requestId prefix check is
unsafe; use the reverse map requestIdToSessionId to find which requestIds belong
to the session instead of comparing requestId.startsWith(prefix). Iterate
requestIdToSessionId to collect all requestIds where sid === sessionId, then for
each id: remove from activeDialogs, remove from pendingDecisions (clear timeout,
resolve with {type: 'cancelled'} and fire onPermissionResult), delete from
requestIdToSessionId, and delete from pendingBySessionId as currently done;
ensure onPendingCountChangeEmitter.fire() is called if any pending/session
entries were removed to preserve the existing notifications.

In `@packages/ai-native/src/browser/acp/webmcp-file-tools.registry.ts`:
- Around line 51-53: resolveWorkspacePath currently just joins workspaceDir and
relativePath, allowing path traversal (e.g., "../") to escape the workspace and
affect file_read/file_write/file_delete; update resolveWorkspacePath to compute
absolute paths (use path.resolve) for both workspaceDir and the joined target,
then verify the target is inside the workspace by checking
path.relative(workspaceAbs, targetAbs) does not start with ".." (or that
targetAbs starts with workspaceAbs + path.sep) and throw an error if it escapes;
return the validated absolute (or normalized) target path so callers like
file_read/file_write/file_delete are protected.

In `@packages/ai-native/src/browser/acp/webmcp-group-registry.ts`:
- Around line 57-76: The executeTool method currently lets errors from
tool.execute bubble up, breaking the expected WebMcpToolResult contract; wrap
the call to tool.execute in an async try/catch (await the promise) inside
executeTool and on any thrown error return a structured failure WebMcpToolResult
(e.g., success: false, error: 'TOOL_EXECUTION_ERROR' or similar, and details
containing the error message/stack) rather than propagating the exception;
update references inside executeTool, notably the local variables group, method,
tool and the final return value, so all execution paths return a
WebMcpToolResult.

In `@packages/ai-native/src/browser/acp/webmcp-groups/file.webmcp-group.ts`:
- Around line 21-26: The resolveWorkspacePath function currently concatenates
paths and returns them without normalization or boundary checks, enabling path
traversal (e.g., "../" or absolute paths) to escape the workspace; update
resolveWorkspacePath to: normalize and resolve the combined path using a proper
path resolver (e.g., path.resolve/path.posix.resolve) against workspaceDir,
canonicalize any symlinks if applicable, then verify that the resolved absolute
path starts with the canonicalized workspaceDir (or throw/return an error) and
only return the resolved path if it is inside the workspace; reference
resolveWorkspacePath to locate and replace the current logic.

In `@packages/ai-native/src/browser/acp/webmcp-tools.registry.ts`:
- Around line 516-546: The option parsing in execute is too permissive: the
current kind derivation uses args.optionId.includes('allow') which can
misclassify arbitrary strings as allowed; update execute to strictly validate
args.optionId against the allowed enum values ('allow_once', 'allow_always',
'reject'), map each explicitly to the kind passed to
permissionBridge.handleUserDecision, and return an INVALID_INPUT error for any
other value; locate the logic around execute, args.optionId, the kind variable
and the call to AcpPermissionBridgeService.handleUserDecision (retrieved via
tryGetService) and replace the includes-based branch with an explicit switch/if
that enforces the whitelist and error path.

In `@packages/ai-native/src/browser/acp/webmcp-utils.ts`:
- Around line 51-53: The current check in webmcp-utils.ts that returns
'DI_ERROR' uses msg.includes('di') which causes false positives; update the
conditional that examines the input variable msg (the branch that returns
'DI_ERROR') to match only explicit DI-related tokens or phrases (e.g., whole
word "di" via word-boundary regex, "dependency injection",
"dependency-injection", "di container", "DIContainer",
"DependencyInjectionError", "injector") instead of a simple substring; keep the
existing injector check but replace msg.includes('di') with a stricter pattern
or set of explicit keyword checks so non-DI messages like "invalid input" are
not misclassified.

In `@packages/ai-native/src/browser/chat/acp-chat-agent.ts`:
- Around line 111-115: The log in getRequestOptions currently prints sensitive
data (API key fragments and full config); change logging to avoid any secret
exposure by only logging non-sensitive booleans/flags (e.g., whether apiKey is
present, whether baseURL is set, model/modelId names if allowed) instead of
apiKey.slice or the full config; update the logger calls in getRequestOptions
and the similar block around lines 169-183 to replace API key/config outputs
with something like "apiKeyConfigured=true/false" and avoid printing partial
keys or full config objects (refer to the getRequestOptions method and the other
logger calls in acp-chat-agent.ts).

In `@packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts`:
- Around line 31-44: The current mapping in mcp-server-proxy.service.ts bypasses
TypeScript checks with "as any" and may return a Zod v3 schema object (not valid
JSON Schema) when .toJSONSchema is absent; update the code in the block that
builds tools so that you detect whether tool.inputSchema has a toJSONSchema
method (e.g., using "typeof (tool.inputSchema as unknown)?.toJSONSchema ===
'function'") and if not, import and call zodToJsonSchema from the
zod-to-json-schema package to convert Zod v3 schemas, or alternatively enforce
Zod v4 in package.json and remove the fallback; ensure you replace the "as any"
cast with a proper type-narrowing check and return a valid JSON Schema in
inputSchema for each tool.

---

Minor comments:
In @.claude/skills/cdp-verification-scenarios/SKILL.md:
- Around line 90-95: The fenced code blocks containing the checklist lines
("Observe actual state (via CDP or WebMCP)" through "Output explicit judgment:
PASS or FAIL") and the other blocks at the indicated ranges are missing a
language tag, causing MD040; update each triple-backtick fence to include a
language token such as ```text or ```md (e.g., change ``` to ```text) for the
blocks that contain the checklist and the blocks at ranges 130-144 and 150-168
so the linter stops flagging them.

In @.claude/skills/dev-loop/SKILL.md:
- Around line 25-29: Add a language identifier to the fenced code block that
contains the flow diagram (the block starting with "Phase 0: 环境准备..."), e.g.
change the opening fence from ``` to ```text so the block becomes ```text ...
```, which will satisfy Markdown lint rule MD040.

In `@docs/superpowers/plans/2026-05-20-acp-node-sdk-refactor.md`:
- Around line 21-57: The markdown has many lint warnings (MD040/MD028) because
fenced code blocks lack language identifiers and blockquotes contain extra blank
lines; update all fenced code blocks in this document (including the sections
showing AcpThread, AcpAgentService, AcpPermissionRpcService, AcpCliBackService,
AcpFileSystemHandler, AcpTerminalHandler diagrams and the long plan sections
referenced) to include an appropriate language token (e.g., text, typescript,
bash) and remove any blank lines inside blockquote sections so blockquotes are
continuous; ensure you apply the same fixes to the other ranges called out
(lines 63-180, 184-237, 241-1240) to eliminate MD040/MD028 warnings.

In `@docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md`:
- Around line 231-547: Several fenced code blocks in the dev-loop plan (the
large Markdown content in
docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md) lack
language identifiers; add appropriate language tags to each triple-backtick
fence (e.g., markdown for Markdown snippets, bash for shell commands, javascript
for CDP/WebMCP snippets, json for configuration) so blocks such as the
Scenario/Phase examples, the shell/git commands, the JSON config, and the CDP
evaluate_script example are explicitly annotated and avoid markdownlint
warnings.

In `@docs/superpowers/specs/2026-05-21-acp-thread-full-delegation-design.md`:
- Around line 33-60: 为两个示意代码块补上语言标识以修复 MD040:在包含接口/类示意图的代码块(显示 IAcpAgentService
和 AcpThread 的那一段)以及文件中另一个相同类型的代码块(评论中提到的 66-68 行附近),把开头的 ``` 改为
```text(或使用更贴近的语言标识),确保示意图的代码块标记为 text 以消除 MD040 警告并保持渲染一致;定位时参考符号
IAcpAgentService 和 AcpThread 以找到对应段落并修改代码块标记。

In `@docs/superpowers/specs/2026-05-22-acp-webmcp-testing-example.md`:
- Around line 45-47: In the markdown file
2026-05-22-acp-webmcp-testing-example.md, many fenced code blocks are missing
language identifiers (triggering MD040); update every triple-backtick block to
include an explicit language tag (e.g., ```text, ```json, ```tsx, ```mermaid)
matching the block content—for example the snippet "Agent 通过 Chrome DevTools MCP
连接到打开的 IDE 页面 (http://localhost:8080)" should be wrapped as ```text; search the
file for all fenced blocks (the ones called out in the review) and add the
appropriate language specifier so linting and rendering are consistent.

In
`@docs/superpowers/specs/2026-05-22-session-bound-permission-dialogs-design.md`:
- Around line 32-52: The fenced code blocks in this document (showing the old
and new flow) lack a language identifier and trigger markdownlint MD040; update
both blocks to use a language tag (for example ```text) so the blocks containing
the flow diagrams (references: the code blocks under the "Node: AcpThread ..."
diagrams) include a language marker and resolve CI/markdownlint failures; ensure
both occurrences around the "Old Flow" and "New Flow" diagrams use the same tag.

In `@docs/superpowers/specs/2026-05-22-webmcp-tool-granularity.md`:
- Around line 31-190: Several fenced code blocks in this spec (e.g., the
examples showing startFileCreation vs createFile, the return-semantic examples,
the usage snippets for searchFiles/getEditorState/runDiagnostics, the
anti-pattern code blocks like navigator.modelContext.registerTool examples, and
the ASCII flowchart) lack language tags and trigger markdownlint MD040; update
each triple-backtick fence to include an appropriate language hint (use
javascript for JS snippets, text for prose/ASCII diagrams, or json when showing
JSON-like returns) so tools and linters correctly highlight and MD040 is
resolved.

In `@docs/superpowers/specs/2026-05-22-webmcp-tool-registration-design.md`:
- Around line 22-325: The doc has many fenced code blocks without language
identifiers which trips the linter; update each triple-backtick block in this
file (including the ASCII diagram, the TypeScript example for
registerAcpWebMCPTools, the flow snippets, tables and shell snippets) to include
an appropriate language tag such as text, typescript, or bash (e.g., replace ```
with ```text or ```typescript / ```bash as appropriate) so examples like the
webmcp-tools.registry.ts snippet and the codegraph_explore/flow blocks are
lint-clean.

In `@docs/superpowers/specs/2026-05-25-dev-loop-design.md`:
- Around line 245-267: The tree snippet claims webmcp-examples.md is moved to
dev-loop/reference/ but the PR intent is to delete it and replace its content
with webmcp-tool-registrar/CODE-PATTERNS.md; update the spec so the entry for
dev-loop/reference/ does not list webmcp-examples.md and instead documents that
webmcp-examples.md is removed and its guidance is superseded by
webmcp-tool-registrar/CODE-PATTERNS.md (referencing webmcp-examples.md,
dev-loop/reference, and webmcp-tool-registrar/CODE-PATTERNS.md to locate the
relevant files).

In
`@docs/superpowers/specs/2026-05-26-background-permission-notification-design.md`:
- Around line 49-82: The fenced code block in the spec lacks a language
identifier which triggers MD040; update the block opening from ``` to include a
language (e.g., ```text or a more specific language) so the markdown linter is
happy—apply this change to the block that documents the Node layer and Browser
layer (containing symbols like AcpThread.handlePermissionRequest,
AcpPermissionCallerService.requestPermission, AcpPermissionBridgeService, and
methods
showPermissionDialog/handleUserDecision/getPendingCountExcludingActive/hasPendingForSession).

In `@packages/ai-native/__test__/browser/permission-dialog-ui.test.tsx`:
- Around line 104-105: The file-level single instance mockPermissionBridge
created via createMockPermissionBridgeService() is reused across tests causing
listener state to leak; remove the top-level instantiation and instead recreate
and inject a fresh mock by calling createMockPermissionBridgeService() inside a
beforeEach, then use that per-test mock when mounting components or wiring
services (reference the mockPermissionBridge identifier and
createMockPermissionBridgeService() factory and update any setups that currently
rely on the shared instance); ensure any afterEach cleanup detaches listeners if
applicable so each test starts with a clean mock.

In `@packages/ai-native/__test__/browser/webmcp-tools.test.ts`:
- Around line 135-141: The test currently asserts
navigator.modelContext!.getTools().length === 12 which is brittle; instead call
navigator.modelContext!.getTools(), filter the result for tools whose name
starts with 'acp_' (e.g. tool.name.match(/^acp_\w+$/) or startsWith('acp_')),
then assert the filtered array length is 12 and loop over the filtered tools to
assert they lack an 'execute' property and their names match the expected
pattern; update assertions to use the filtered list (references: getTools,
navigator.modelContext, tool.name).

In `@packages/ai-native/__test__/node/acp-thread-status-caller.test.ts`:
- Around line 64-68: The test currently uses a synchronous check
(expect(...).not.toThrow()) which won't catch Promise rejections; change the
assertion to await the async call and assert it resolves (e.g., await
expect(service.notifyThreadStatusChange('session-1',
'working')).resolves.toBeUndefined()) so the mocked rejection from
mockRpcClient.$onThreadStatusChange is properly asserted as swallowed by
notifyThreadStatusChange.

In `@packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx`:
- Line 199: Remove the stray direct rendering of the boolean value
item.hasPendingPermission in the AcpChatHistory component (it causes "true" to
appear in the DOM); locate the JSX that contains "{item.hasPendingPermission}"
and either delete that expression or replace it with a proper conditional
UI/indicator (e.g., render an icon or text only when item.hasPendingPermission
is true using a conditional render) so the boolean itself is not output to the
DOM.

In `@packages/ai-native/src/browser/acp/webmcp-groups/terminal.webmcp-group.ts`:
- Around line 60-84: The schema exposes a name field but it is never used when
creating the terminal; update the execute logic to pass params.name into
terminalController.createTerminal (e.g., include a displayName or name property
in the options object) so the declared name becomes effective, or if you prefer
not to support naming remove the name entry from the schema/description to avoid
misleading callers; locate the execute function where
terminalController.createTerminal is called and either add the name propagation
(params.name as string) to the createTerminal options or remove the name schema
entry.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md`:
- Around line 79-87: The example Promise stores a pending decision with a bogus
cast `undefined as unknown as NodeJS.Timeout`; change the shape so
`pendingDecisions` entries make `timeout` optional (e.g., timeout?:
NodeJS.Timeout) or omit `timeout` entirely in the "no-auto-timeout" path instead
of asserting types; update the code that reads or clears
`pendingDecisions.get(requestId)?.timeout` to handle the optional field and any
callers of the structure (look for uses of pendingDecisions, requestId, and
timeout in resolve/clear logic) so no type-unsafe assertions remain.

In `@packages/ai-native/src/browser/acp/build-agent-process-config.ts`:
- Around line 8-35: The current buildAcpAgentProcessConfig uses
input.userPreferences.nodePath || undefined which collapses an explicit empty
string to undefined; update the assignment for nodePath in
buildAcpAgentProcessConfig to preserve an explicit empty string (i.e. only
convert to undefined when nodePath is strictly undefined) by replacing the
truthy-coercion with an explicit check against undefined, and adjust tests or
add a new test to cover the distinct behavior between "" and undefined for
userPreferences.nodePath.

In `@packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx`:
- Around line 215-220: Remove the commented-out debug span block inside the
AcpChatHistory component: delete the entire commented JSX that contains
data-testid={`thread-status-${item.id}`} and references item.threadStatus and
item.loading so the file no longer contains commented debugging code; after
removal, run a quick lint/format pass to ensure the JSX remains well-formed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7f64c691-5eab-46d8-a516-f8f9369cfb16

📥 Commits

Reviewing files that changed from the base of the PR and between f0c4c12 and 97c61d4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (108)
  • .claude/skills/cdp-verification-scenarios/SKILL.md
  • .claude/skills/contract-dev/SKILL.md
  • .claude/skills/dev-loop/SKILL.md
  • .gitignore
  • docs/superpowers/plans/2026-05-20-acp-node-sdk-refactor.md
  • docs/superpowers/plans/2026-05-21-acp-thread-full-delegation-impl.md
  • docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md
  • docs/superpowers/plans/2026-05-25-dev-loop-skill-implementation.md
  • docs/superpowers/plans/2026-05-26-acp-webmcp-groups.md
  • docs/superpowers/specs/2026-05-21-acp-thread-full-delegation-design.md
  • docs/superpowers/specs/2026-05-22-acp-webmcp-testing-example.md
  • docs/superpowers/specs/2026-05-22-session-bound-permission-dialogs-acceptance.md
  • docs/superpowers/specs/2026-05-22-session-bound-permission-dialogs-design.md
  • docs/superpowers/specs/2026-05-22-webmcp-tool-granularity.md
  • docs/superpowers/specs/2026-05-22-webmcp-tool-registration-design.md
  • docs/superpowers/specs/2026-05-25-dev-loop-design.md
  • docs/superpowers/specs/2026-05-26-acp-webmcp-groups-design.md
  • docs/superpowers/specs/2026-05-26-background-permission-notification-design.md
  • packages/ai-native/__test__/browser/acp/build-agent-process-config.test.ts
  • packages/ai-native/__test__/browser/acp/permission-bridge-session.test.ts
  • packages/ai-native/__test__/browser/permission-dialog-ui.test.tsx
  • packages/ai-native/__test__/browser/webmcp-tools.test.ts
  • packages/ai-native/__test__/node/acp-agent-request-handler.test.ts
  • packages/ai-native/__test__/node/acp-agent.service.test.ts
  • packages/ai-native/__test__/node/acp-cli-back.test.ts
  • packages/ai-native/__test__/node/acp-cli-client.test.ts
  • packages/ai-native/__test__/node/acp-cli-process-manager.test.ts
  • packages/ai-native/__test__/node/acp-file-system-handler.test.ts
  • packages/ai-native/__test__/node/acp-permission-caller.test.ts
  • packages/ai-native/__test__/node/acp-terminal-handler.test.ts
  • packages/ai-native/__test__/node/acp-thread-status-caller.test.ts
  • packages/ai-native/__test__/node/acp-webmcp-handler.test.ts
  • packages/ai-native/__test__/node/acp/acp-spawn-config.test.ts
  • packages/ai-native/__test__/node/acp/acp-thread.test.ts
  • packages/ai-native/__test__/node/permission-routing.test.ts
  • packages/ai-native/__tests__/node/acp/cli-agent-process-manager.test.ts
  • packages/ai-native/package.json
  • packages/ai-native/src/browser/acp/acp-permission-rpc.service.ts
  • packages/ai-native/src/browser/acp/acp-thread-status-rpc.service.ts
  • packages/ai-native/src/browser/acp/acp-webmcp-rpc.service.ts
  • packages/ai-native/src/browser/acp/build-agent-process-config.ts
  • packages/ai-native/src/browser/acp/components/AcpChatHistory.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatInput.tsx
  • packages/ai-native/src/browser/acp/components/AcpChatViewHeader.tsx
  • packages/ai-native/src/browser/acp/index.ts
  • packages/ai-native/src/browser/acp/permission-bridge.service.ts
  • packages/ai-native/src/browser/acp/permission-dialog-container.tsx
  • packages/ai-native/src/browser/acp/webmcp-file-tools.registry.ts
  • packages/ai-native/src/browser/acp/webmcp-group-registry.ts
  • packages/ai-native/src/browser/acp/webmcp-groups/editor.webmcp-group.ts
  • packages/ai-native/src/browser/acp/webmcp-groups/file.webmcp-group.ts
  • packages/ai-native/src/browser/acp/webmcp-groups/terminal.webmcp-group.ts
  • packages/ai-native/src/browser/acp/webmcp-tools.registry.ts
  • packages/ai-native/src/browser/acp/webmcp-utils.ts
  • packages/ai-native/src/browser/ai-core.contribution.ts
  • packages/ai-native/src/browser/chat/acp-chat-agent.ts
  • packages/ai-native/src/browser/chat/chat-manager.service.acp.ts
  • packages/ai-native/src/browser/chat/chat-model.ts
  • packages/ai-native/src/browser/chat/chat.internal.service.acp.ts
  • packages/ai-native/src/browser/chat/chat.view.acp.tsx
  • packages/ai-native/src/browser/chat/default-acp-config-provider.ts
  • packages/ai-native/src/browser/components/ChatHistory.acp.tsx
  • packages/ai-native/src/browser/components/ChatHistory.tsx
  • packages/ai-native/src/browser/components/acp/MentionInput.tsx
  • packages/ai-native/src/browser/components/acp/chat-history.module.less
  • packages/ai-native/src/browser/components/chat-history.module.less
  • packages/ai-native/src/browser/components/permission-dialog-widget.tsx
  • packages/ai-native/src/browser/index.ts
  • packages/ai-native/src/browser/mcp/config/mcp-config.service.ts
  • packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts
  • packages/ai-native/src/browser/preferences/schema.ts
  • packages/ai-native/src/common/tool-invocation-registry.ts
  • packages/ai-native/src/node/acp/acp-agent.service.ts
  • packages/ai-native/src/node/acp/acp-cli-back.service.ts
  • packages/ai-native/src/node/acp/acp-cli-client.service.ts
  • packages/ai-native/src/node/acp/acp-error.ts
  • packages/ai-native/src/node/acp/acp-permission-caller.service.ts
  • packages/ai-native/src/node/acp/acp-spawn-config.ts
  • packages/ai-native/src/node/acp/acp-thread-status-caller.service.ts
  • packages/ai-native/src/node/acp/acp-thread.ts
  • packages/ai-native/src/node/acp/acp-update-types.ts
  • packages/ai-native/src/node/acp/acp-webmcp-caller.service.ts
  • packages/ai-native/src/node/acp/acp-webmcp-handler.ts
  • packages/ai-native/src/node/acp/cli-agent-process-manager.ts
  • packages/ai-native/src/node/acp/handlers/agent-request.handler.ts
  • packages/ai-native/src/node/acp/handlers/file-system.handler.ts
  • packages/ai-native/src/node/acp/handlers/terminal.handler.ts
  • packages/ai-native/src/node/acp/index.ts
  • packages/ai-native/src/node/acp/permission-routing.service.ts
  • packages/ai-native/src/node/index.ts
  • packages/ai-native/src/node/mcp-server.sse.ts
  • packages/ai-native/src/node/mcp-server.stdio.ts
  • packages/ai-native/src/node/openai-compatible/openai-compatible-language-model.ts
  • packages/core-browser/src/webmcp-polyfill.ts
  • packages/core-browser/src/webmcp-types.ts
  • packages/core-common/src/settings/ai-native.ts
  • packages/core-common/src/types/ai-native/acp-types.ts
  • packages/core-common/src/types/ai-native/agent-types.ts
  • packages/core-common/src/types/ai-native/index.ts
  • packages/core-node/src/connection.ts
  • packages/i18n/src/common/en-US.lang.ts
  • packages/i18n/src/common/zh-CN.lang.ts
  • packages/startup/entry/sample-modules/ai-native/WelcomePage.tsx
  • packages/terminal-next/src/browser/index.ts
  • packages/terminal-next/src/browser/webmcp-tools.registry.ts
  • scripts/verify-mcp-server.js
  • test/bdd/message-flow.scenario.md
  • test/bdd/permission-dialog.scenario.md
💤 Files with no reviewable changes (4)
  • packages/ai-native/tests/node/acp/cli-agent-process-manager.test.ts
  • packages/ai-native/test/node/acp-cli-client.test.ts
  • packages/ai-native/src/browser/components/ChatHistory.acp.tsx
  • packages/ai-native/test/node/acp-cli-process-manager.test.ts

Comment on lines +159 to +167
// Subscribe to active session changes
useEffect(() => {
const unsubscribe = permissionBridgeService.onActiveSessionChange((sessionId) => {
setActiveSessionId(sessionId);
});
// Initialize with current session
setActiveSessionId(permissionBridgeService.getActiveSession());
return unsubscribe;
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

useEffect 清理函数示例写法有误,当前返回值不是 cleanup function。

这里应返回一个函数(调用 dispose()),否则实现时容易出现类型/清理行为错误。

建议修正
 useEffect(() => {
-  const unsubscribe = permissionBridgeService.onActiveSessionChange((sessionId) => {
+  const disposable = permissionBridgeService.onActiveSessionChange((sessionId) => {
     setActiveSessionId(sessionId);
   });
   // Initialize with current session
   setActiveSessionId(permissionBridgeService.getActiveSession());
-  return unsubscribe;
+  return () => disposable.dispose();
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Subscribe to active session changes
useEffect(() => {
const unsubscribe = permissionBridgeService.onActiveSessionChange((sessionId) => {
setActiveSessionId(sessionId);
});
// Initialize with current session
setActiveSessionId(permissionBridgeService.getActiveSession());
return unsubscribe;
}, []);
// Subscribe to active session changes
useEffect(() => {
const disposable = permissionBridgeService.onActiveSessionChange((sessionId) => {
setActiveSessionId(sessionId);
});
// Initialize with current session
setActiveSessionId(permissionBridgeService.getActiveSession());
return () => disposable.dispose();
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-22-session-bound-permission-dialogs.md` around
lines 159 - 167, useEffect 中当前直接 return unsubscribe 导致不是一个 cleanup
function;把订阅的返回值(unsubscribe)包成一个函数并在该函数中调用其 dispose() 方法。定位到使用
useEffect、permissionBridgeService.onActiveSessionChange、unsubscribe、permissionBridgeService.getActiveSession
和 setActiveSessionId 的代码,把 return unsubscribe; 改为 return () =>
unsubscribe.dispose();(若 onActiveSessionChange 返回的是一个函数而非对象则改为 return () =>
unsubscribe();)。

Comment on lines +518 to +523
const parts = method.split('/');
if (parts.length !== 3 || parts[0] !== '' || parts[1] === '') {
return { success: false, error: 'TOOL_NOT_FOUND', details: `Invalid method: ${method}` };
}
const groupName = parts[1];
const toolAction = parts[2];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

_opensumi/{group}/{action} 解析条件有误,会误判合法方法。

当前判断把 parts[0] 期望成空字符串,但 _opensumi/file/readparts[0]_opensumi,会导致合法调用被拒绝。建议改成显式校验前缀段。

建议修正
- const parts = method.split('/');
- if (parts.length !== 3 || parts[0] !== '' || parts[1] === '') {
+ const parts = method.split('/');
+ if (parts.length !== 3 || parts[0] !== '_opensumi' || !parts[1] || !parts[2]) {
   return { success: false, error: 'TOOL_NOT_FOUND', details: `Invalid method: ${method}` };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parts = method.split('/');
if (parts.length !== 3 || parts[0] !== '' || parts[1] === '') {
return { success: false, error: 'TOOL_NOT_FOUND', details: `Invalid method: ${method}` };
}
const groupName = parts[1];
const toolAction = parts[2];
const parts = method.split('/');
if (parts.length !== 3 || parts[0] !== '_opensumi' || !parts[1] || !parts[2]) {
return { success: false, error: 'TOOL_NOT_FOUND', details: `Invalid method: ${method}` };
}
const groupName = parts[1];
const toolAction = parts[2];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-26-acp-webmcp-groups.md` around lines 518 -
523, The parsing logic incorrectly expects parts[0] to be an empty string which
rejects valid methods like "_opensumi/file/read"; update the conditional around
method, parts, groupName and toolAction so it explicitly checks that
parts.length === 3, parts[0] === '_opensumi' (the prefix), and parts[1] is
non-empty, and only then assign groupName = parts[1] and toolAction = parts[2];
adjust the error branch to return TOOL_NOT_FOUND when these explicit checks
fail.

Comment on lines +60 to +63
- **Group 方法**(`_opensumi/{group}/*`)在 `load_group` 时动态注册,`unload_group` 时注销
- Agent 调用未加载的 group 方法时,收到标准 JSON-RPC "Method not found"(code: -32601)错误

动态注册/注销的实现:ACP Server 维护一个方法注册表,`load_group` 时将方法添加到注册表并通知客户端方法可用(通过 ACP notification),`unload_group` 时移除并通知不可用。
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

错误语义与调用机制描述与当前实现不一致。

这里写的是“未加载返回 -32601 / 动态注册扩展方法”,但现有实现路径是统一 extMethod 路由并返回 TOOL_NOT_LOADED(业务错误对象)。建议文档改为与实现一致,或在实现层补齐到文档约定,否则 SDK/测试会按错契约编写。

Also applies to: 207-207

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-26-acp-webmcp-groups-design.md` around lines
60 - 63, The doc's described behavior (dynamic registration of
`_opensumi/{group}/*` on load_group/unload_group and JSON-RPC -32601 when not
loaded) is inconsistent with the current implementation which routes all calls
through extMethod and returns a business error TOOL_NOT_LOADED; update either
the docs or the implementation to match: either change the docs in this spec to
describe the extMethod routing and TOOL_NOT_LOADED response (referencing
load_group/unload_group and ACP Server notification semantics), or modify the
implementation so ACP Server actually registers/unregisters methods at
load_group/unload_group and returns standard JSON-RPC Method not found (-32601)
when a group method is not loaded; ensure you reference extMethod,
TOOL_NOT_LOADED, load_group, unload_group and ACP Server in the chosen fix so
SDK/tests follow the correct contract.

Comment on lines +23 to +30
jest.mock('stream/web', () => ({
ReadableStream: class MockReadableStream {
constructor() {}
},
WritableStream: class MockWritableStream {
constructor() {}
},
}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 检查被测实现与测试中的 stream/web 导入/Mock 路径 =="
rg -n "node:stream/web|stream/web" \
  packages/ai-native/src/node/acp/acp-thread.ts \
  packages/ai-native/__test__/node/acp/acp-thread.test.ts

Repository: opensumi/core

Length of output: 362


修正 stream/web 的 Jest mock 路径以匹配真实导入(node:stream/web
packages/ai-native/src/node/acp/acp-thread.ts 使用 import ... from 'node:stream/web',但测试在 packages/ai-native/__test__/node/acp/acp-thread.test.tsjest.mock('stream/web', ...),两者不一致会导致 mock 可能未生效、测试意外依赖真实实现。

可参考的修复 diff
-jest.mock('stream/web', () => ({
+jest.mock('node:stream/web', () => ({
   ReadableStream: class MockReadableStream {
     constructor() {}
   },
   WritableStream: class MockWritableStream {
     constructor() {}
   },
 }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/node/acp/acp-thread.test.ts` around lines 23 -
30, The Jest mock for stream/web doesn't match the actual import used in
acp-thread.ts (which imports from 'node:stream/web'), so update the mock target
in acp-thread.test.ts to jest.mock('node:stream/web', ...) and ensure it exports
MockReadableStream and MockWritableStream matching the original mock's shape;
locate the existing MockReadableStream/MockWritableStream definitions in the
test and replace the module specifier string only so the mock applies to the
import used by the code under test.

Comment on lines +272 to +290
it('should transition to disconnected on process exit', async () => {
(thread as any)._processRunning = true;
(thread as any)._connected = true;

const exitMock = createMockChildProcess(12345);
(thread as any)._childProcess = exitMock;

exitMock.on('exit', (code: number | null, signal: string | null) => {
(thread as any)._processRunning = false;
(thread as any)._connected = false;
(thread as any)._status = 'disconnected';
});

exitMock.emit('exit', 0, null);

expect((thread as any)._processRunning).toBe(false);
expect((thread as any)._connected).toBe(false);
expect(thread.status).toBe('disconnected');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

“process exit” 用例在测试里自行改状态,未验证 AcpThread 真实逻辑。

Line 279-Line 283 通过测试代码自行监听 exit 并直接写私有字段,这会导致即使产品代码未处理 exit,用例也可能通过。建议触发 AcpThread 内部注册的退出链路再断言状态。

可参考的修复方向
 it('should transition to disconnected on process exit', async () => {
-  (thread as any)._processRunning = true;
-  (thread as any)._connected = true;
-
-  const exitMock = createMockChildProcess(12345);
-  (thread as any)._childProcess = exitMock;
-
-  exitMock.on('exit', (code: number | null, signal: string | null) => {
-    (thread as any)._processRunning = false;
-    (thread as any)._connected = false;
-    (thread as any)._status = 'disconnected';
-  });
-
-  exitMock.emit('exit', 0, null);
+  await (thread as any).startProcess();
+  ((thread as any)._childProcess as any).emit('exit', 0, null);

   expect((thread as any)._processRunning).toBe(false);
   expect((thread as any)._connected).toBe(false);
   expect(thread.status).toBe('disconnected');
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/__test__/node/acp/acp-thread.test.ts` around lines 272 -
290, The test currently installs its own 'exit' handler and directly mutates
private fields, which bypasses AcpThread's real exit handling; instead assign
the mock child process to (thread as any)._childProcess (using
createMockChildProcess), do not add an 'exit' listener in the test, then emit
'exit' on that mock so AcpThread's internal handler (which updates
_processRunning, _connected and status) runs; assert (thread as
any)._processRunning is false, (thread as any)._connected is false and
thread.status is 'disconnected'. Use the same symbols: _childProcess,
_processRunning, _connected and status on the AcpThread instance so the test
verifies the class's real logic.

Comment on lines +21 to +26
function resolveWorkspacePath(workspaceDir: string, relativePath: string): string {
if (relativePath.startsWith('/')) {
return relativePath;
}
return `${workspaceDir}/${relativePath}`.replace(/\/+/g, '/');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

限制路径逃逸到工作区之外

Line 21-26 的路径拼接没有做“归一化 + 工作区边界校验”,../ 或绝对路径可直接越过 workspace 根目录,导致 _opensumi/file/* 工具可访问非预期路径。

🔧 建议修复
-import { AppConfig } from '`@opensumi/ide-core-browser`';
+import { AppConfig, path } from '`@opensumi/ide-core-browser`';
@@
-function resolveWorkspacePath(workspaceDir: string, relativePath: string): string {
-  if (relativePath.startsWith('/')) {
-    return relativePath;
-  }
-  return `${workspaceDir}/${relativePath}`.replace(/\/+/g, '/');
+function resolveWorkspacePath(workspaceDir: string, inputPath: string): string {
+  const workspaceRoot = path.resolve(workspaceDir);
+  const resolved = path.resolve(workspaceRoot, inputPath);
+  if (resolved !== workspaceRoot && !resolved.startsWith(`${workspaceRoot}${path.sep}`)) {
+    throw new Error(`Path escapes workspace root: ${inputPath}`);
+  }
+  return resolved;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/webmcp-groups/file.webmcp-group.ts` around
lines 21 - 26, The resolveWorkspacePath function currently concatenates paths
and returns them without normalization or boundary checks, enabling path
traversal (e.g., "../" or absolute paths) to escape the workspace; update
resolveWorkspacePath to: normalize and resolve the combined path using a proper
path resolver (e.g., path.resolve/path.posix.resolve) against workspaceDir,
canonicalize any symlinks if applicable, then verify that the resolved absolute
path starts with the canonicalized workspaceDir (or throw/return an error) and
only return the resolved path if it is inside the workspace; reference
resolveWorkspacePath to locate and replace the current logic.

Comment on lines +516 to +546
inputSchema: {
type: 'object',
properties: {
requestId: { type: 'string', description: 'The requestId of the pending permission dialog.' },
optionId: { type: 'string', description: 'The option to select: "allow_once", "allow_always", or "reject".' },
},
required: ['requestId', 'optionId'],
},
execute: async (args: { requestId: string; optionId: string }) => {
if (!args.requestId) {
return { success: false, error: 'INVALID_INPUT', details: 'requestId is required' };
}
if (!args.optionId) {
return { success: false, error: 'INVALID_INPUT', details: 'optionId is required' };
}
const permissionBridge = tryGetService(container, AcpPermissionBridgeService) as AcpPermissionBridgeService;
if (!permissionBridge) {
return {
success: false,
error: 'SERVICE_UNAVAILABLE',
details: 'AcpPermissionBridgeService not registered in DI container',
};
}
try {
const kind: string = args.optionId.includes('allow')
? args.optionId.includes('always')
? 'allow_always'
: 'allow_once'
: 'reject';
permissionBridge.handleUserDecision(args.requestId, args.optionId, kind as any);
return { success: true, result: { requestId: args.requestId, optionId: args.optionId } };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

权限选项解析过于宽松,可能把非法值误判为允许

Line 540-545 使用 includes('allow') 推断 kind,会把非白名单值错误映射到 allow 分支。这里应做严格枚举校验并显式映射。

🔧 建议修复
       inputSchema: {
         type: 'object',
         properties: {
           requestId: { type: 'string', description: 'The requestId of the pending permission dialog.' },
-          optionId: { type: 'string', description: 'The option to select: "allow_once", "allow_always", or "reject".' },
+          optionId: {
+            type: 'string',
+            enum: ['allow_once', 'allow_always', 'reject'],
+            description: 'The option to select: "allow_once", "allow_always", or "reject".',
+          },
         },
         required: ['requestId', 'optionId'],
       },
@@
-          const kind: string = args.optionId.includes('allow')
-            ? args.optionId.includes('always')
-              ? 'allow_always'
-              : 'allow_once'
-            : 'reject';
+          const allowed = new Set(['allow_once', 'allow_always', 'reject']);
+          if (!allowed.has(args.optionId)) {
+            return { success: false, error: 'INVALID_INPUT', details: 'invalid optionId' };
+          }
+          const kind = args.optionId as 'allow_once' | 'allow_always' | 'reject';
           permissionBridge.handleUserDecision(args.requestId, args.optionId, kind as any);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/webmcp-tools.registry.ts` around lines 516
- 546, The option parsing in execute is too permissive: the current kind
derivation uses args.optionId.includes('allow') which can misclassify arbitrary
strings as allowed; update execute to strictly validate args.optionId against
the allowed enum values ('allow_once', 'allow_always', 'reject'), map each
explicitly to the kind passed to permissionBridge.handleUserDecision, and return
an INVALID_INPUT error for any other value; locate the logic around execute,
args.optionId, the kind variable and the call to
AcpPermissionBridgeService.handleUserDecision (retrieved via tryGetService) and
replace the includes-based branch with an explicit switch/if that enforces the
whitelist and error path.

Comment on lines +51 to +53
if (msg.includes('di') || msg.includes('injector')) {
return 'DI_ERROR';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

收窄 DI 错误匹配条件,避免误分类

Line 51msg.includes('di') 会把大量非 DI 错误误判为 DI_ERROR(如 invalid input)。应改为更明确的关键词。

🔧 建议修改
-    if (msg.includes('di') || msg.includes('injector')) {
+    if (
+      msg.includes('injector') ||
+      msg.includes('dependency injection') ||
+      msg.includes('service not found')
+    ) {
       return 'DI_ERROR';
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (msg.includes('di') || msg.includes('injector')) {
return 'DI_ERROR';
}
if (
msg.includes('injector') ||
msg.includes('dependency injection') ||
msg.includes('service not found')
) {
return 'DI_ERROR';
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/acp/webmcp-utils.ts` around lines 51 - 53, The
current check in webmcp-utils.ts that returns 'DI_ERROR' uses msg.includes('di')
which causes false positives; update the conditional that examines the input
variable msg (the branch that returns 'DI_ERROR') to match only explicit
DI-related tokens or phrases (e.g., whole word "di" via word-boundary regex,
"dependency injection", "dependency-injection", "di container", "DIContainer",
"DependencyInjectionError", "injector") instead of a simple substring; keep the
existing injector check but replace msg.includes('di') with a stricter pattern
or set of explicit keyword checks so non-DI messages like "invalid input" are
not misclassified.

Comment on lines +111 to +115
this.logger.log(
`[ACP Chat] getRequestOptions: model=${model}, modelId=${modelId}, apiKey=${
apiKey ? apiKey.slice(0, 8) + '***' : '(empty)'
}, baseURL=${baseURL}, maxTokens=${maxTokens}`,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

避免在日志中输出敏感配置(API Key/完整配置对象)

这里把 API Key 前缀和完整 config 直接写入日志,存在敏感信息泄露风险(即使截断也不建议输出密钥片段)。建议只记录是否已配置等布尔信息。

🔧 建议修改
-    this.logger.log(
-      `[ACP Chat] getRequestOptions: model=${model}, modelId=${modelId}, apiKey=${
-        apiKey ? apiKey.slice(0, 8) + '***' : '(empty)'
-      }, baseURL=${baseURL}, maxTokens=${maxTokens}`,
-    );
+    this.logger.log(
+      `[ACP Chat] getRequestOptions: model=${model}, modelId=${modelId}, hasApiKey=${Boolean(apiKey)}, hasBaseURL=${Boolean(baseURL)}, maxTokens=${maxTokens}`,
+    );
@@
-      this.logger.log(`[ACP Chat] invoke: sessionId=${sessionId}, config=${JSON.stringify(config)}`);
+      this.logger.log(`[ACP Chat] invoke: sessionId=${sessionId}, hasAgentSessionConfig=${Boolean(config)}`);
@@
-      this.logger.log(
-        `[ACP Chat] invoking aiBackService.requestStream: agentSessionConfig=${!!requestOptions.agentSessionConfig}, apiKey=${
-          requestOptions.apiKey ? requestOptions.apiKey.slice(0, 8) + '***' : '(empty)'
-        }`,
-      );
+      this.logger.log(
+        `[ACP Chat] invoking aiBackService.requestStream: agentSessionConfig=${!!requestOptions.agentSessionConfig}, hasApiKey=${Boolean(requestOptions.apiKey)}`,
+      );

Also applies to: 169-183

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/chat/acp-chat-agent.ts` around lines 111 -
115, The log in getRequestOptions currently prints sensitive data (API key
fragments and full config); change logging to avoid any secret exposure by only
logging non-sensitive booleans/flags (e.g., whether apiKey is present, whether
baseURL is set, model/modelId names if allowed) instead of apiKey.slice or the
full config; update the logger calls in getRequestOptions and the similar block
around lines 169-183 to replace API key/config outputs with something like
"apiKeyConfigured=true/false" and avoid printing partial keys or full config
objects (refer to the getRequestOptions method and the other logger calls in
acp-chat-agent.ts).

Comment on lines +31 to +44
const tools = await this.mcpServerRegistry.getMCPTools().map((tool) => {
// Use Zod v4's built-in toJSONSchema() instead of zodToJsonSchema (v3-only)
const jsonSchema =
typeof (tool.inputSchema as any).toJSONSchema === 'function'
? (tool.inputSchema as any).toJSONSchema()
: tool.inputSchema;

return {
name: tool.name,
description: tool.description,
inputSchema: zodToJsonSchema(tool.inputSchema),
inputSchema: jsonSchema,
providerName: BUILTIN_MCP_SERVER_NAME,
}),
);
};
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

类型断言绕过了类型检查,且 fallback 可能在 Zod 3.x 中返回无效 JSON Schema。

Line 35 使用 as any 绕过 TypeScript 类型检查以调用 .toJSONSchema()。若 tool.inputSchema 是 Zod 3.x 的 schema 对象且没有此方法,会 fallback 到 line 36 直接返回 schema 实例,但 Zod 3.x 的 schema 对象不是有效的 JSON Schema——需要通过 zod-to-json-schema 库转换。

建议:

  1. 引入 zod-to-json-schema 作为 Zod 3.x 的 fallback,或
  2. 强制要求 Zod 4.x(与 package.json 注释一致)。
🔧 建议的 Zod 3.x 兼容性修复(若需保留 3.x 支持)
+import { zodToJsonSchema } from 'zod-to-json-schema';
+
 async $getBuiltinMCPTools() {
   const tools = await this.mcpServerRegistry.getMCPTools().map((tool) => {
-    // Use Zod v4's built-in toJSONSchema() instead of zodToJsonSchema (v3-only)
-    const jsonSchema =
-      typeof (tool.inputSchema as any).toJSONSchema === 'function'
-        ? (tool.inputSchema as any).toJSONSchema()
-        : tool.inputSchema;
+    const jsonSchema =
+      typeof (tool.inputSchema as any).toJSONSchema === 'function'
+        ? (tool.inputSchema as any).toJSONSchema()
+        : zodToJsonSchema(tool.inputSchema);

     return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts` around lines
31 - 44, The current mapping in mcp-server-proxy.service.ts bypasses TypeScript
checks with "as any" and may return a Zod v3 schema object (not valid JSON
Schema) when .toJSONSchema is absent; update the code in the block that builds
tools so that you detect whether tool.inputSchema has a toJSONSchema method
(e.g., using "typeof (tool.inputSchema as unknown)?.toJSONSchema ===
'function'") and if not, import and call zodToJsonSchema from the
zod-to-json-schema package to convert Zod v3 schemas, or alternatively enforce
Zod v4 in package.json and remove the fallback; ensure you replace the "as any"
cast with a proper type-narrowing check and return a valid JSON Schema in
inputSchema for each tool.

@lulusir lulusir changed the title Enhance WebMCP tool metadata, RPC reliability and add thread status services 「WIP」Enhance WebMCP tool metadata, RPC reliability and add thread status services May 27, 2026
- Delete 17 stale superpowers plan and spec files from docs/
- Add permission pending indicator to AcpChatHistory
- Refine load_group response to include tool metadata
- Fix webmcp-tools.registry.ts terminal tool descriptions
- Update permission-dialog-ui and acp-webmcp-handler tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant